

#include "stdafx.h"

#include "jsonparser.h"


/*  David Jones 
** @CEDriverwiz
** Embedded MVP
** Sportronics
** Australia
** http://embedded101.com/Blogs/David-Jones
** The above contains a number of blogs on this topic.
** Project: CEJSON on Codeplex https://cejson.codeplex.com
** Related project: ARDJSON on Codeplex https://ardjson.codeplex.com
*/

/* Gets Telemetry records from Micrsoft Azure Mobile Service
** Telemetry  is the sample app based upon the ToDoItem app that can be generated when an Azure Mobile Service is created
** This app gets the same subset of records that the app displays.
**
** This inludes a JSon Stream Parser, implemented as a State Machine.
** The parser is called character by character, not teh whole response at once.
** Hence it processes as a stream.
*/


//For working out error source in code:
int ErrNo = -1;

/////////////////////////////////////////////////////////////

/////////////////////////////////////////////////////////////
/// Json Parse code
/// Parses JSon string both for records and name value pairs within records
/// Parser parses a stream and is implemented as a State machine

// Parser state machine states:
enum Expecting
{
	startOfArray,
	startOfRecord,
	startOfName,
	gettingName,
	nameValueSeparator,
	startOfValue,
	gettingValue,
	gettingEndOfValueORRecord,
	gotEndOfRecord,
	gettingRecordSeparator,
	done,
	error,

	gettingString,
	gettingBoolean,
	gettingInteger,
	gettingFloat,
	gettingNull
};


// Some of the states expect to parse a specific character
// For those states that character is in the follwoing arary; ndexed by Expecting enums
// char  ExpectArray[11] = "[{\"X:XX,X]";  //X is don't care

//The current state of the State Machine
Expecting parseState = startOfArray;

//Assume by default getting an array, ie starts with [
//If starts with { then just getting one record so jump to that mode and done one that record is interpretted.
BOOL gGettingArray = true;

// For many states when its parse requierment is satified: state <-- state++
void IncrementState()
{
	//output.print("Expect Now: ");
	//output.println(parseState);
	parseState = (Expecting)((int)parseState + 1);
	//output.print("Expect Next: ");
	//output.println(parseState);
}

// For states where state increments by one if the expected character is the
// current one in the stream.
BOOL Expect(char c)
{
	char  ExpectArray[11] = "[{\"X:XX,X]";  //X is don't care
	if (c == ExpectArray[parseState])
	{

		IncrementState();
		return true;
	}
	else if (('{' == c) && (startOfArray == parseState))
	{
		//Permit parsing of records only
		parseState = startOfName;
	}
	else
	{
		//Expectation wasn't satified so error
		output.print(F("Expected: "));
		output.println(ExpectArray[parseState]);
		output.print(F("Got: "));
		output.println(c);
		parseState = error;
		output.print(F("Expect Next Err: "));
		output.println(parseState);

		ErrNo = 10;
		return false;
	}
	return true;

}


NameValue CurrentNameValue;

/*
Parses a JSon array of records of name value pairs
*/
BOOL ParseJsonString(char c)
{
	output.PushState();
	//output.Disable();
	//If result is false then subsequent parses are aborted
	BOOL result = true;
	ErrNo = -1;
	int strnlength;

	// Signal to format print a name-vale pair after switch statement
	BOOL gotANameValuePair = false;

	switch (parseState)
	{
		case startOfArray:
			result = Expect(c);
			if (result)
			{
				//Start of array so no records yet.
				RecordNo = 0;
				output.println(F("\r\n1: Starting parse of array."));
			}
			//If the string starts with { just getting one record rather than an array
			if (parseState == startOfName)
			{
				RecordNo++;
				//Start of new record so no no name value pairs yet
				NameValueIndex = 0;
				output.println(F("\r\n2: New record."));
				gGettingArray = false;
			}
			break;
		case startOfRecord:
			result = Expect(c);
			if (result)
			{
				RecordNo++;
				//Start of new record so no no name value pairs yet
				NameValueIndex = 0;
				output.println(F("\r\n2: New record."));
			}
			break;
		case startOfName:
			result = Expect(c);
			if (result)
			{
				NameValueIndex++;
				int index = REC_NO(RecordNo - 1, NameValueIndex - 1);
				if (1 == 0) //(index >= MAX_NO_NAME_VALUE_PAIRS)
				{
					output.println(F("\r\n2: Max No of Name-value pairs exceeded"));
					ErrNo = 11;
					NameValueIndex--;
					RecordNo--;
					result = false;
				}
				else
				{
					//output.println(F("3: New record Name-Value Pair."));

					strcpy(CurrentNameValue.Name, "");
					strcpy(CurrentNameValue.StringValue, "");
					CurrentNameValue.BooleanValue = false;
					CurrentNameValue.IntegerValue = 0;
					CurrentNameValue.FloatValue = 0;
					CurrentNameValue.DType = tUnknown;
				}
			}
			break;
		case gettingName:
			if (c != '\"')
			{
				//output.println(F("4: Getting Name-Value Name."));
				//Append to name
				strnlength = strlen(CurrentNameValue.Name);
				if (strnlength == 0)
				{
					if ((isalpha(c)) || (c == '_'))  //Can lead only with alpha and _
					{
						strnlength = strlen(CurrentNameValue.Name);
						CurrentNameValue.Name[strnlength] = c;
						CurrentNameValue.Name[strnlength + 1] = '\0';
					}
					else
					{
						result = false;
						ErrNo = 101;
					}
				}
				else if ((isalpha(c)) || (c == '_') || (isdigit(c)))  //Can use _ or alpha in name
				{
					strnlength = strlen(CurrentNameValue.Name);
					CurrentNameValue.Name[strnlength] = c;
					CurrentNameValue.Name[strnlength + 1] = '\0';
				}
				else

				{
					if (parseState != done)
					{
						result = false;
						ErrNo = 1;
					}
				}
			}
			else
			{
				//Got end of name
				IncrementState();
			}
			break;
		case nameValueSeparator:
			result = Expect(c);
			break;
		case startOfValue:
			//output.println(F("Getting value."));
			CurrentNameValue.StringValue[0] = '\0';
			switch (c)
			{
			case '\"':
				parseState = gettingString;
				break;
			case 'T':
				parseState = gettingBoolean;
				strnlength = strlen(CurrentNameValue.StringValue);
				CurrentNameValue.StringValue[strnlength] = c;
				CurrentNameValue.StringValue[strnlength + 1] = '\0';
				break;
			case 't':
				parseState = gettingBoolean;
				strnlength = strlen(CurrentNameValue.StringValue);
				CurrentNameValue.StringValue[strnlength] = c;
				CurrentNameValue.StringValue[strnlength + 1] = '\0';
				break;
			case 'F':
				parseState = gettingBoolean;
				strnlength = strlen(CurrentNameValue.StringValue);
				CurrentNameValue.StringValue[strnlength] = c;
				CurrentNameValue.StringValue[strnlength + 1] = '\0';
				break;
			case 'f':
				parseState = gettingBoolean;
				strnlength = strlen(CurrentNameValue.StringValue);
				CurrentNameValue.StringValue[strnlength] = c;
				CurrentNameValue.StringValue[strnlength + 1] = '\0';
				break;
			case 'N':
				parseState = gettingNull;
				strnlength = strlen(CurrentNameValue.StringValue);
				CurrentNameValue.StringValue[strnlength] = c;
				CurrentNameValue.StringValue[strnlength + 1] = '\0';
				break;
			case 'n':
				parseState = gettingNull;
				strnlength = strlen(CurrentNameValue.StringValue);
				CurrentNameValue.StringValue[strnlength] = c;
				CurrentNameValue.StringValue[strnlength + 1] = '\0';
				break;
			default:
				if (isdigit(c))
				{
					parseState = gettingInteger;
					strnlength = strlen(CurrentNameValue.StringValue);
					CurrentNameValue.StringValue[strnlength] = c;
					CurrentNameValue.StringValue[strnlength + 1] = '\0';
				}
				else
				{
					ErrNo = 2;
					result = false;
				}
				break;
			}
			break;
		case gettingString:
			if (c == '\"')
			{
				gotANameValuePair = true;
				CurrentNameValue.DType = tString;
				
				//If the string is is in the correct datatime format then extract the datetime data
				if (ISAzMSDateTime(CurrentNameValue.StringValue))
				{
					tm createTime;
					//Save the timestamp
					CurrentNameValue.DateValue = ParseDateTime(CurrentNameValue.StringValue, &createTime);
					CurrentNameValue.DType = tDate;
				}
				parseState = gettingEndOfValueORRecord;
			}
			else
			{ //Added these brackets 5/4/15
				strnlength = strlen(CurrentNameValue.StringValue);
				CurrentNameValue.StringValue[strnlength] = c;
				CurrentNameValue.StringValue[strnlength + 1] = '\0';
			}
			break;
		case gettingBoolean:
			if ((c == '}') || (c == ','))
			{
				if (c == '}')
					parseState = gotEndOfRecord;
				else
				{
					//Comma, so start new name-value pair
					parseState = startOfName;
				}
				//CurrentNameValue.StringValue.toLowerCase();
					{
						int i = -1;
						while (CurrentNameValue.StringValue[++i])
						{
							CurrentNameValue.StringValue[i] = tolower(CurrentNameValue.StringValue[i]);
						}
					}

					if (strcmp(CurrentNameValue.StringValue, "false") == 0)
					{
						CurrentNameValue.BooleanValue = false;
						CurrentNameValue.DType = tBoolean;
						gotANameValuePair = true;
					}
					else if (strcmp(CurrentNameValue.StringValue, "true") == 0)
					{
						CurrentNameValue.BooleanValue = true;
						CurrentNameValue.DType = tBoolean;
						gotANameValuePair = true;
					}
					else
					{
						ErrNo = 3;
						result = false;
					}
			}
			else
			{
				strnlength = strlen(CurrentNameValue.StringValue);
				CurrentNameValue.StringValue[strnlength] = c;
				CurrentNameValue.StringValue[strnlength + 1] = '\0';
			}
			break;
		case gettingNull:
			if ((c == '}') || (c == ','))
			{
				if (c == '}')
					parseState = gotEndOfRecord;
				else
				{
					//Comma, so start new name-value pair
					parseState = startOfName;
				}
				//CurrentNameValue.StringValue.toLowerCase();
					{
						int i = 0;
						while (CurrentNameValue.StringValue[i])
						{
							CurrentNameValue.StringValue[i] = tolower(CurrentNameValue.StringValue[i++]);
						}
					}
					if (strcmp(CurrentNameValue.StringValue, "null") == 0)
					{
						CurrentNameValue.BooleanValue = false;
						CurrentNameValue.DType = tNull;
						gotANameValuePair = true;
					}
					else
					{
						ErrNo = 4;
						result = false;
					}
			}
			else
			{
				strnlength = strlen(CurrentNameValue.StringValue);
				CurrentNameValue.StringValue[strnlength] = c;
				CurrentNameValue.StringValue[strnlength + 1] = '\0';
			}
			break;
		case gettingInteger:
			if ((c == '}') || (c == ','))
			{
				int val = atoi(CurrentNameValue.StringValue);
				CurrentNameValue.IntegerValue = val;
				CurrentNameValue.DType = tInteger;
				gotANameValuePair = true;
				if (c == '}')
				{
					parseState = gotEndOfRecord;
				}
				else
					//Comma, so start new name-value pair
					parseState = startOfName;
			}
			else if (c == '.')
			{
				parseState = gettingFloat;
				strnlength = strlen(CurrentNameValue.StringValue);
				CurrentNameValue.StringValue[strnlength] = c;
				CurrentNameValue.StringValue[strnlength + 1] = '\0';
			}
			else if (isdigit(c))
			{
				strnlength = strlen(CurrentNameValue.StringValue);
				CurrentNameValue.StringValue[strnlength] = c;
				CurrentNameValue.StringValue[strnlength + 1] = '\0';
			}
			else
			{
				ErrNo = 5;
				result = false;
			}
			break;
		case gettingFloat:
			if ((c == '}') || (c == ','))
			{
				//Parse number
				CurrentNameValue.FloatValue = (float) atof(CurrentNameValue.StringValue);
				CurrentNameValue.DType = tFloat;
				gotANameValuePair = true;
				if (c == '}')
				{
					parseState = gotEndOfRecord;
				}
				else
					//Comma, so start new name-value pair
					parseState = startOfName;
			}
			else if (isdigit(c))
			{
				strnlength = strlen(CurrentNameValue.StringValue);
				CurrentNameValue.StringValue[strnlength] = c;
				CurrentNameValue.StringValue[strnlength + 1] = '\0';
			}
			else
			{
				ErrNo = 6;
				result = false;
			}
			break;
		case gettingEndOfValueORRecord:
			//PREV: gettingString
			//This state is only taken (as next state) after  the end of a string literal
			if ((c == '}') || (c == ','))
			{
				if (c == '}')
				{
					parseState = gotEndOfRecord;
					if (!gGettingArray)
					{
						output.println(F("End of Record."));
						output.println(F("End of Array.\r\n"));
						parseState = done;
					}
				}
				else
				{
					parseState = startOfName;
				}
			}
			else
			{
				ErrNo = 7;
				result = false;
			}
			break;
		case gotEndOfRecord:
			// PREV : getting<value type except string> or gettingEndOfValueORRecord
			// After a record can get comma which means more name-values to come
			// Or ] which means at end of array and string
			if (c == ',')
			{
				//Got comma so expect another record
				parseState = startOfRecord;
				output.println(F("End of Record."));
			}
			else if (c == ']')
			{
				//Got ] so end of array. We are done
				output.println(F("End of Record."));
				output.println(F("End of Array.\r\n"));
				parseState = done;
			}
			else
			{
				ErrNo = 8;
				result = false;
			}
			break;
		default:
		{
			ErrNo = 9;
			result = false;
		}
	}




	if ((gotANameValuePair) && (result))
	{
		if (RecordNo == 1)
			numNameValuePairsPerRecord = NameValueIndex;
		AddNameValue(CurrentNameValue, RecordNo, NameValueIndex - 1);
	}
	else if (!result)
	{
		// If any errors then signal halt to state machine.
		parseState = error;
		output.print(F("An error has occured. ErrNo:"));
		output.println(ErrNo);
		/*output.print(F("Name:"));
		output.println(CurrentNameValue.StringValue);
		output.print(F("StringValue:"));
		CurrentNameValue.StringValue[8] = '\0';
		output.println(CurrentNameValue.StringValue);*/
		if (ErrNo == 11)
		{
			output.println(F("Couldn't parse all name-value pairs"));
		}
	}
	//output.print(F("Parseout: "));
	//output.println(parseState);

	output.PopState();
	return result;
}

